﻿using System;
using System.Threading.Tasks;
using System.Windows.Input;

namespace Neudesic.Pulse.Infrastructure
{

    /// <summary>
    /// DelegateCommand that operates asynchronously with optional completion and exception callbacks
    /// </summary>
    public class AsyncDelegateCommand<TResult> : ICommand
    {

        // TODO: Thread safety from callers.

        Task<TResult> task;
        Func<bool> canExecute;
        Func<TResult> action;
        Action<TResult> completed;
        Action<Exception> exception;

        /// <summary>
        /// Initializes a new instance of the <see cref="AsyncDelegateCommand" /> class.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="canExecute">The can execute.</param>
        /// <param name="completed">The completed.</param>
        /// <param name="exception">The exception.</param>
        public AsyncDelegateCommand(Func<TResult> action, Func<bool> canExecute = null, Action<TResult> completed = null, Action<Exception> exception = null)
        {

            this.action = action;
            this.completed = completed;
            this.exception = exception;

            InitializeTask();

            if (canExecute == null)
                this.canExecute = new Func<bool>(() => true );

            else
                this.canExecute = canExecute;

        }

        private void InitializeTask()
        {

            task = new Task<TResult>(
                () =>
                {
                    CommandManager.InvalidateRequerySuggested();  // Force a requery of this ICommand CanExecute.
                    return action();
                }
                );

            task.ContinueWith(
                (t) =>
                {
                    // TODO: Handle exception occurring in the completed handler
                    if (completed != null)
                    {
                        completed(t.Result);
                    }
                    CommandManager.InvalidateRequerySuggested(); // Force a requery of this ICommand CanExecute.
                }, TaskContinuationOptions.OnlyOnRanToCompletion
                );

            task.ContinueWith(
                (t) =>
                {
                    if (exception != null)
                    {
                        exception(t.Exception.InnerException);
                    }
                }, TaskContinuationOptions.OnlyOnFaulted
                );

        }

        /// <summary>
        /// Determines whether this instance can execute the specified parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            return canExecute == null ? canExecute() : task.Status != TaskStatus.Running && canExecute();
        }

        /// <summary>
        /// Event for discovering changes in CanExecute property.
        /// </summary>
        /// <value>The can execute changed.</value>
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        /// <summary>
        /// Executes the specified parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        public void Execute(object parameter)
        {
            if (task.Status != TaskStatus.Created)
                InitializeTask();

            task.Start();
        }

        /// <summary>
        /// Raises the can execute changed.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CommandManager.InvalidateRequerySuggested();
        }

        public void Cancel()
        {
            throw new NotImplementedException(); // TODO with cancellation token
        }
    }
}
